/**
 * 
 */
package jmine.tec.test.proxy.spring;

import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.proxy.builder.ProxyBuilder;
import jmine.tec.proxy.chain.ChainElement;
import jmine.tec.proxy.chain.ChainedInvocationHandler;
import jmine.tec.test.proxy.AbstractMockObjectChainElement;
import jmine.tec.test.proxy.MockObjectChainElement;
import jmine.tec.test.proxy.MockObjectDelegateChainElement;

import org.springframework.beans.factory.config.AbstractFactoryBean;

/**
 * Cria um 'mock' de um tipo - pode ser uma classe ou um tipo não final
 * 
 * @author takeshi
 */
public class MockProxyFactoryBean extends AbstractFactoryBean {

    private Class<?> type;

    private ClassLoader classLoader;

    private Map<String, Object> mockValues;

    private boolean forward;

    private List<ChainElement> aditionalChainElements = Collections.emptyList();

    private Object delegate;

    /**
     * @param delegate the delegate to set
     */
    public void setDelegate(final Object delegate) {
        this.delegate = delegate;
    }

    /**
     * @param aditionalChainElements the aditionalChainElements to set
     */
    public void setAditionalChainElements(final List<ChainElement> aditionalChainElements) {
        this.aditionalChainElements = aditionalChainElements;
    }

    /**
     * @return the forward
     */
    public boolean isForward() {
        return this.forward;
    }

    /**
     * @param forward the forward to set
     */
    public void setForward(final boolean forward) {
        this.forward = forward;
    }

    /**
     * @return the mockValues
     */
    public final Map<String, Object> getMockValues() {
        if (this.mockValues == null) {
            this.mockValues = new HashMap<String, Object>();
        }
        return this.mockValues;
    }

    /**
     * @return the classLoader
     */
    public final ClassLoader getClassLoader() {
        if (this.classLoader == null) {
            this.classLoader = Thread.currentThread().getContextClassLoader();
        }
        return this.classLoader;
    }

    /**
     * @param classLoader the classLoader to set
     */
    public final void setClassLoader(final ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    /**
     * @return the type
     */
    public final Class<?> getType() {
        return this.type;
    }

    /**
     * @param type the type to set
     */
    public final void setType(final Class<?> type) {
        this.type = type;
    }

    /**
     * @return {@link Object}
     * @throws Exception e
     * @see org.springframework.beans.factory.config.AbstractFactoryBean#createInstance()
     */
    @Override
    protected Object createInstance() throws Exception {
        final AbstractMockObjectChainElement ce;
        if (this.isForward()) {
            ce = new MockObjectDelegateChainElement();
        } else {
            ce = new MockObjectChainElement();
        }
        ce.setValues(this.getMockValues());

        final ChainElement[] eles = new ChainElement[this.aditionalChainElements.size() + 1];
        int i = 0;
        for (final ChainElement chainElement : this.aditionalChainElements) {
            eles[i++] = chainElement;
        }
        eles[i] = ce;

        if (this.type.isInterface()) {
            return Proxy.newProxyInstance(this.getClassLoader(), new Class[]{ this.type },
                    new ChainedInvocationHandler(this.delegate, eles));
        } else {
            return ProxyBuilder.proxyFor(null, this.type, eles);
        }
    }

    /**
     * @return {@link Class}
     * @see org.springframework.beans.factory.FactoryBean#getObjectType()
     */
    @Override
    public Class<?> getObjectType() {
        return this.type;
    }

    /**
     * Se <code>true</code>, invocara o metodo no delegate
     * 
     * @param mockValues the mockValues to set
     */
    public final void setMockValues(final Map<String, Object> mockValues) {
        this.mockValues = mockValues;
    }

}
